#
# This file is a part of the open-eBackup project.
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at
# http://mozilla.org/MPL/2.0/.
#
# Copyright (c) [2024] Huawei Technologies Co.,Ltd.
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#

import locale
from datetime import datetime

from oracle import logger
from common.common import read_result_file
from common.file_common import delete_file
from oracle.common.windows_common import write_tmp_file, execute_windows_sqlplus_cmd, set_db_silence_sql, \
    check_rman_status, check_sqlplus_status, create_empty_file
from oracle.common.constants import ScriptExitCode, PluginPathConstant, RETURN_INVALID, ShellAuthTypeEnum, \
    OracleStartTypeEnum


def check_db_status(pid, db_instance, db_user, db_password):
    # 改方法疑似未被使用
    logger.info(f"Check db status, pid:{pid}.")
    result_file = f"{PluginPathConstant.RESULT_TMP_FILE_PREFIX}{pid}"
    delete_file(result_file)
    ret = check_sqlplus_status()
    if not ret:
        logger.error(f"Check sqlplus status failed, pid:{pid}")
    ret = check_rman_status()
    if not ret:
        logger.error(f"check rman status failed, pid:{pid}")
    tmp_sql = f"{PluginPathConstant.WINDOWS_TMP_PATH}/get_database_status_{pid}.sql"
    tmp_rst = f"{PluginPathConstant.WINDOWS_TMP_PATH}/get_database_status_{pid}.txt"
    content = f"SPOOL {tmp_rst}\n" \
              f"select status from v$instance;\n" \
              f"SPOOL OFF\n" \
              f"exit;"
    write_tmp_file(tmp_sql, content)
    set_db_silence_sql(tmp_sql)
    silent_params = {
        "db_user": db_user,
        "db_password": db_password,
        "is_silence": 1
    }
    ret_code = execute_windows_sqlplus_cmd(pid, silent_params, tmp_sql, tmp_rst, db_instance)
    if ret_code != ScriptExitCode.SUCCESS:
        logger.info(f"Check db status success, pid:{pid}")
        delete_file(tmp_sql)
        delete_file(tmp_rst)
        return ScriptExitCode.SUCCESS
    encoding = locale.getdefaultlocale()[1]
    logger.error(f"Check db status failed, pid:{pid}, ret: {ret_code}, "
                 f"error is: {read_result_file(tmp_rst, encoding=encoding)}.")
    delete_file(tmp_sql)
    delete_file(tmp_rst)
    return RETURN_INVALID


def start_open(pid, db_instance, db_user, db_password):
    logger.info(f"Begin to start DB, pid:{pid}.")
    tmp_sql = f"{PluginPathConstant.WINDOWS_TMP_PATH}/StartDB{pid}.sql"
    tmp_rst = f"{PluginPathConstant.WINDOWS_TMP_PATH}/DtartDBRST{pid}.txt"
    content = f"SPOOL {tmp_rst}\n" \
              f"startup;\n" \
              f"SPOOL OFF\n" \
              f"exit;"
    write_tmp_file(tmp_sql, content)
    silent_params = {
        "db_user": db_user,
        "db_password": db_password,
        "is_silence": 1
    }
    ret_code = execute_windows_sqlplus_cmd(pid, silent_params, tmp_sql, tmp_rst, db_instance)
    encoding = locale.getdefaultlocale()[1]
    content = read_result_file(tmp_rst, encoding=encoding).splitlines()
    if ret_code != ScriptExitCode.SUCCESS:
        delete_file(tmp_sql)
        delete_file(tmp_rst)
        logger.error(f"Start DB failed, ret={ret_code}, errmsg={content}")
        return False
    for line in content:
        if "already-running ORACLE" in line:
            logger.info(f"Database instance {db_instance} is OPEN, pid:{pid}.")
            delete_file(tmp_sql)
            delete_file(tmp_rst)
            return True
    delete_file(tmp_sql)
    delete_file(tmp_rst)
    logger.info(f"Start DB success, pid:{pid}")
    return True


def unix_timestamp_to_date(time_stamp):
    """
    将时间戳转为日期时间格式
    :pit_time: 时间戳
    """
    pit_time = int(time_stamp)
    logger.info(f"Enter function unix_timestamp_to_date, pit_time={pit_time}")
    data_time = datetime.fromtimestamp(pit_time)
    return data_time


def get_instance_auth_type(pid, instance_name):
    """
        Description: get oracle instance auth type
        Arguments: instance_name oracle instance name
        Globals: None
        Returns: -1 for fail, other nums for auth tyoe 0 is db 1 is os
    """
    logger.info(f"Enter function get_instance_auth_type, pid:{pid}.")
    if not instance_name:
        logger.error(f"Instance name is none canot get auth type, pid:{pid}")
        return RETURN_INVALID
    check_oracle_auth_sql = f"{PluginPathConstant.WINDOWS_TMP_PATH}/check_oracle_auth_sql_{pid}.sql"
    check_oracle_auth_rst = f"{PluginPathConstant.WINDOWS_TMP_PATH}/check_oracle_auth_rst_{pid}.txt"
    create_empty_file(check_oracle_auth_rst)
    sql_cmd = f"SPOOL {check_oracle_auth_rst}\n" \
              f"exit;\n" \
              f"SPOOL OFF"
    write_tmp_file(check_oracle_auth_sql, sql_cmd)
    set_db_silence_sql(check_oracle_auth_sql)
    params = {
        "is_silence": 1,
        "db_user": "",
        "db_password": "",
    }
    ret_code = execute_windows_sqlplus_cmd(pid, params, check_oracle_auth_sql, check_oracle_auth_rst, instance_name)
    delete_file(check_oracle_auth_sql)
    delete_file(check_oracle_auth_rst)
    if ret_code != ScriptExitCode.SUCCESS:
        logger.info(f"Instance {instance_name} is DB auth type.")
        delete_file(f"{PluginPathConstant.WINDOWS_TMP_PATH}/result_errordetail{pid}")
        return ShellAuthTypeEnum.DATABASE_AUTH
    logger.info(f"Instance {instance_name} is OS auth type.")
    return ShellAuthTypeEnum.OS_AUTH


def compare_oracle_version(pid, version_a, version_b):
    logger.info(f"{version_a} compare with {version_b}, pid:{pid}")
    num = 0
    version_a_list = version_a.split(".")
    version_b_list = version_b.split(".")
    while num < 5:
        pre_version_a = version_a_list[num]
        pre_version_b = version_b_list[num]
        num += 1
        if pre_version_a == pre_version_b:
            continue
        if pre_version_a > pre_version_b:
            logger.info(f"{version_a} greater then {version_b}, pid:{pid}")
            return 1
        else:
            logger.info(f"{version_a} lower then {version_b}, pid:{pid}")
            return 2
    logger.info(f"{version_a} equal {version_b}, pid:{pid}")
    return 0


def modify_cluster_property(pid, instance_name, status, params):
    logger.info(f"Enter function modify_cluster_property, pid:{pid}.")
    tmp_sql = f"{PluginPathConstant.WINDOWS_TMP_PATH}/TEMP{pid}.sql"
    tmp_rst = f"{PluginPathConstant.WINDOWS_TMP_PATH}/TEMPRST{pid}.txt"
    tmp_sql_content = f"SPOOL {tmp_rst}\n" \
                      f"alter system set cluster_database={status} scope=spfile;\n" \
                      f"shutdown immediate;\n" \
                      f"SPOOL OFF\n" \
                      f"exit;\n"
    write_tmp_file(tmp_sql, tmp_sql_content)
    set_db_silence_sql(tmp_sql)
    logger.info(f"Begin to modify cluster property, pid:{pid}.")
    ret_code = execute_windows_sqlplus_cmd(pid, params, tmp_sql, tmp_rst, instance_name)
    encoding = locale.getdefaultlocale()[1]
    rst_content = read_result_file(tmp_rst, encoding=encoding)
    if ret_code != 0 and ret_code != 41:
        logger.info(f"Modify cluster-{instance_name} failed, ret_code={ret_code}, error is {rst_content}, pid:{pid}.")
        delete_file(tmp_sql)
        delete_file(tmp_rst)
        return False
    logger.info(f"Modify cluster-{instance_name} success, pid:{pid}.")
    delete_file(tmp_sql)
    delete_file(tmp_rst)
    return True


def start_mount(pid, instance_name, db_user, db_password):
    logger.info(f"Enter function start_mount, pid:{pid}.")
    tmp_sql = f"{PluginPathConstant.WINDOWS_TMP_PATH}/TEMP{pid}.sql"
    tmp_rst = f"{PluginPathConstant.WINDOWS_TMP_PATH}/TEMPRST{pid}.txt"
    tmp_sql_content = "startup mount\n" \
                      "exit;"
    write_tmp_file(tmp_sql, tmp_sql_content)
    set_db_silence_sql(tmp_sql)
    silent_params = {
        "db_user": db_user,
        "db_password": db_password,
        "is_silence": 1
    }
    ret_code = execute_windows_sqlplus_cmd(pid, silent_params, tmp_sql, tmp_rst, instance_name)
    if ret_code != ScriptExitCode.SUCCESS:
        encoding = locale.getdefaultlocale()[1]
        logger.error(f"Alter database mount failed, error is {read_result_file(tmp_rst, encoding=encoding)}, pid:{pid}")
        delete_file(tmp_sql)
        delete_file(tmp_rst)
        return RETURN_INVALID
    logger.info(f"Alter database mount success, pid:{pid}.")
    delete_file(tmp_sql)
    delete_file(tmp_rst)
    return ScriptExitCode.SUCCESS


def check_database_cdb_type(pid, instance_name, ora_version, params):
    logger.info(f"Enter function start_mount, pid:{pid}.")
    oracle_is_cdb = 1
    # get Oracle version and check if it's 12
    if int(ora_version) < 12:
        logger.info(f"Oracle version({ora_version}) not supports CDB, pid:{pid}.")
        return 1
    tmp_sql = f"{PluginPathConstant.WINDOWS_TMP_PATH}/check_oracle_cdb_type_{pid}.sql"
    tmp_rst = f"{PluginPathConstant.WINDOWS_TMP_PATH}/check_oracle_cdb_type_{pid}.rst"
    # get CDB type
    tmp_sql_content = f"SPOOL {tmp_rst}\n" \
                      f"select cdb from v$database;\n" \
                      f"SPOOL OFF\n" \
                      f"exit;"
    write_tmp_file(tmp_sql, tmp_sql_content)
    set_db_silence_sql(tmp_sql)

    logger.info("Exec SQL to get CDB type of instance, pid:{pid}.")
    ret_code = execute_windows_sqlplus_cmd(pid, params, tmp_sql, tmp_rst, instance_name)
    encoding = locale.getdefaultlocale()[1]
    rst_content = read_result_file(tmp_rst, encoding=encoding).splitlines()
    if ret_code != ScriptExitCode.SUCCESS:
        logger.error(f"Get database cdb type failed, error is {rst_content}")
        delete_file(tmp_sql)
        delete_file(tmp_rst)
        return oracle_is_cdb
    delete_file(tmp_sql)
    delete_file(tmp_rst)
    for line in rst_content:
        if "YES" in line:
            oracle_is_cdb = 0
            logger.info(f"The instance {instance_name} is a CDB, pid:{pid}")
            return oracle_is_cdb
    return oracle_is_cdb


def open_all_pdbs(pid, instance_name, params):
    logger.info(f"Enter function open_all_pdbs, pid:{pid}.")
    tmp_sql = f"{PluginPathConstant.WINDOWS_TMP_PATH}/open_all_pdbs{pid}.sql"
    tmp_rst = f"{PluginPathConstant.WINDOWS_TMP_PATH}/open_all_pdbs{pid}.rst"
    tmp_sql_content = f"SPOOL {tmp_rst}\n" \
                      f"alter pluggable database all open;\n" \
                      f"SPOOL OFF\n" \
                      f"exit;"
    write_tmp_file(tmp_sql, tmp_sql_content)
    set_db_silence_sql(tmp_sql)
    ret_code = execute_windows_sqlplus_cmd(pid, params, tmp_sql, tmp_rst, instance_name)
    encoding = locale.getdefaultlocale()[1]
    rst_content = read_result_file(tmp_rst, encoding=encoding)
    if ret_code != ScriptExitCode.SUCCESS:
        logger.error(f"Open PDB failed, ret=$ret_code, error is {rst_content}, pid:{pid}")
        delete_file(tmp_sql)
        delete_file(tmp_rst)
        return ret_code
    delete_file(tmp_sql)
    delete_file(tmp_rst)
    logger.info(f"Open PDB success, pid:{pid}")
    return ScriptExitCode.SUCCESS


def get_database_status(pid, db_instance, db_user, db_password, is_start_db):
    logger.info(f"Start get database status, pid:{pid}.")
    tmp_sql = f"{PluginPathConstant.WINDOWS_TMP_PATH}/get_database_status_{pid}.sql"
    tmp_rst = f"{PluginPathConstant.WINDOWS_TMP_PATH}/get_database_status_{pid}.txt"
    content = f"SPOOL {tmp_rst}\n" \
              f"select status from v$instance;\n" \
              f"SPOOL OFF\n" \
              f"exit;"
    write_tmp_file(tmp_sql, content)
    silent_params = {
        "db_user": db_user,
        "db_password": db_password,
        "is_silence": 1
    }
    ret_code = execute_windows_sqlplus_cmd(pid, silent_params, tmp_sql, tmp_rst, db_instance)
    encoding = locale.getdefaultlocale()[1]
    if ret_code != ScriptExitCode.SUCCESS:
        logger.info(f"Database instance {db_instance} is CLOSE, pid:{pid}, "
                    f"err is {read_result_file(tmp_rst, encoding=encoding)}.")
        delete_file(tmp_sql)
        delete_file(tmp_rst)
        return False
    content = read_result_file(tmp_rst, encoding=encoding).splitlines()
    for line in content:
        if is_start_db == str(OracleStartTypeEnum.NOT_START_DB.value):
            if "MOUNTED" in line:
                logger.info(f"No need to open database. Database instance {db_instance} is MOUNTED, pid:{pid}.")
                delete_file(tmp_sql)
                delete_file(tmp_rst)
                return True
            else:
                continue
        if "OPEN" in line:
            logger.info(f"Database instance {db_instance} is OPEN, pid:{pid}.")
            delete_file(tmp_sql)
            delete_file(tmp_rst)
            return True
    delete_file(tmp_sql)
    delete_file(tmp_rst)
    logger.info(f"Get database status filed, pid: {pid}, instance: {db_instance}.")
    return False


def get_temp_file_info(pid, instance_name, db_user, db_password, rst_file):
    get_temp_file_tmp_sql = f"{PluginPathConstant.WINDOWS_TMP_PATH}/get_temp_file_info_{pid}.sql"
    sql_cmd = f"SPOOL {rst_file}\n" \
              f"set linesize 999;\n" \
              "col NAME for a255;\n" \
              "select FILE#, NAME from v$tempfile order by FILE#;\n" \
              "SPOOL OFF;\n" \
              "exit;\n"
    write_tmp_file(get_temp_file_tmp_sql, sql_cmd)
    set_db_silence_sql(get_temp_file_tmp_sql)
    params = {
        'is_silence': 1,
        'db_user': db_user,
        'db_password': db_password
    }

    ret_code = execute_windows_sqlplus_cmd(pid, params, get_temp_file_tmp_sql, rst_file, instance_name)
    delete_file(get_temp_file_tmp_sql)
    if ret_code != ScriptExitCode.SUCCESS:
        encoding = locale.getdefaultlocale()[1]
        logger.error(f"Get oracle temp file failed, pid: {pid}, ret: {ret_code}, "
                    f"error is: {read_result_file(rst_file, encoding=encoding)}.")
        return ret_code
    logger.info(f"Get oracle temp log file success, pid: {pid}.")
    return ret_code
